package danceDoll;

import bvh.BVHBone;
import com.jme3.animation.Bone;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import java.util.List;

/**
 * Handles the Animation. Requires a BVHController with the Animationdata and a Skeleton.
 * Runs the given animation frame per frame and transists between animations when a new is given.
 * Corrects the Axis from every Bone, matching für the BVH-Files.
 * @author Vincent Bosche
 * @version 1.0
 */
public class AnimationControl {

    private Model m;
    private Initialize init;
    private BVHController currentAnimation = null, nextAnimation = null;
    private int currentFrame;
    private float frame = 0;

    /**
     * Creats an Animation by using the SkelettonDebog & AnimControl from the Model
     * @param m Model with Skeletton and AnimControl
     */
    AnimationControl(Model m, Initialize init) {
        this.m = m;
        this.init = init;
    }

    /**
     * Searches from the Bone-List the specific Bone by Name.
     * @param name Bone-Name to find
     * @param b Bone to Start (shuld be the rootbone)
     * @return the selected Bone, if not found null
     */
    public Bone getBone(String name, Bone b) {
        if (b.getName().equals(name)) {
            return b;
        }

        Bone found = null;
        for (Bone bb : b.getChildren()) {
            if (bb.getName().equals(name)) {
                found = bb;
            } else {
                for (Bone b2 : bb.getChildren()) {
                    if (b2.getName().equals(name)) {
                        found = b2;
                    } else if (found == null) {
                        found = getBone(name, b2);
                    }
                }
            }
        }
        return found;
    }

     /**
     * Reads the currentFrame from the BVH-Bone and sets the Rotaions to the Skeleton-Bone
     * @param bone Bone from the Skeleton
     * @param bvhBone Bone from the BVH-File (contains the new values)
     */
    public void animBone(Bone bone, BVHBone bvhBone) {
        if (bvhBone != null && bone != null /*&& bvhBone.getName().equals(bone.getName())*/) {

            float rx = 0;
            float ry = 0;
            float rz = 0;
            
            Vector3f translation = new Vector3f();
            // Hüfte
            if (bone.getName().equals("Hip")) {        
                //x = X-Werte in der BVH (nach links-/rechts+)
                //y = neg Z-Werte in der BVH (nach vorne+/hinten-)
                //Z = Y-Werte in der BVH (nach oben+/unten-)
                translation.x = bvhBone.getChannels().get(0).getValues().get(currentFrame);
                translation.y = -bvhBone.getChannels().get(2).getValues().get(currentFrame);
                translation.z = bvhBone.getChannels().get(1).getValues().get(currentFrame);
                
                //rx = Drehen Y-Werte in der BVH
                //ry = Neigen neg X-Werte in der BVH
                //rz = Rollen Z-Werte in der BVH
                rx = bvhBone.getChannels().get(4).getValues().get(currentFrame);
                ry = -bvhBone.getChannels().get(3).getValues().get(currentFrame);
                rz = bvhBone.getChannels().get(5).getValues().get(currentFrame);
   
            } else {
                // Alle anderen Knochen
                rx = bvhBone.getChannels().get(0).getValues().get(currentFrame);
                ry = bvhBone.getChannels().get(1).getValues().get(currentFrame);
                rz = bvhBone.getChannels().get(2).getValues().get(currentFrame);
            }
            
            bone.setUserControl(true);
            
            Quaternion rotationX = new Quaternion();
            Quaternion rotationY = new Quaternion();
            Quaternion rotationZ = new Quaternion();

            // Korrekturen der Rotationsachsen
  
            if (bone.getName().equals("RClavicle")) {
                //rx = neg X-Werte in der BVH
                //ry = neg Y-Werte in der BVH
                //rz = Z-Werte in der BVH 
                rx = -rx;
                ry = -ry;
            }
            if (bone.getName().equals("RShoulder")) { 
                //rx = neg X-Werte in der BVH
                //ry = neg Y-Werte in der BVH
                //rz = Z-Werte in der BVH   
                rx = -rx;
                ry = -ry;
            }
            if (bone.getName().equals("RForearm")) {
                //rx = neg X-Werte in der BVH)
                //ry = neg Y-Werte in der BVH)
                //rz = Z-Werte in der BVH) 
                rx = -rx;
                ry = -ry;
            } 
            if (bone.getName().equals("LClavicle")) {
                //rx = neg X-Werte in der BVH)
                //ry = neg Y-Werte in der BVH)
                //rz = Z-Werte in der BVH)               
                rx = -rx;
                ry = -ry;
            }
            if (bone.getName().equals("LShoulder")) {
                //rx = neg X-Werte in der BVH)
                //ry = neg Y-Werte in der BVH)
                //rz = Z-Werte in der BVH) 
                rx = -rx;
                ry = -ry;
            }
            if (bone.getName().equals("LForearm")) {
                //rx = neg X-Werte in der BVH)
                //ry = neg Y-Werte in der BVH)
                //rz = Z-Werte in der BVH)
                rx = -rx;
                ry = -ry;
            }
        
            if (bone.getName().equals("RShin")) {
                //rx = neg Y-Werte in der BVH)
                //ry = neg X-Werte in der BVH)
                //rz = neg Z-Werte in der BVH)
                float temp = rx;
                rx = -ry;
                ry = -temp;
                rz = -rz;    
                
            }
            if (bone.getName().equals("RFoot")) {
                //rx = Z-Werte in der BVH)
                //ry = neg X-Werte in der BVH)
                //rz = neg Y-Werte in der BVH)
                float temp = rx;
                rx = rz;
                rz = -ry;
                ry = -temp;
            }
        
            if (bone.getName().equals("LShin")) {
                //rx = neg Y-Werte in der BVH)
                //ry = neg X-Werte in der BVH)
                //rz = neg Z-Werte in der BVH)
                float temp = rx;
                rx = -ry;
                ry = -temp;
                rz = -rz;
            }
            if (bone.getName().equals("LFoot")) {
                //rx = neg Z-Werte in der BVH)
                //ry = neg X-Werte in der BVH)
                //rz = Y-Werte in der BVH)
                float temp = rx;
                rx = -rz;
                rz = ry;
                ry = -temp;
            }

            //Berechnen der Rotationen
            
            rotationX = rotationX.fromAngleAxis(rx * FastMath.DEG_TO_RAD, Vector3f.UNIT_X);
            rotationY = rotationY.fromAngleAxis(ry * FastMath.DEG_TO_RAD, Vector3f.UNIT_Y);
            rotationZ = rotationZ.fromAngleAxis(rz * FastMath.DEG_TO_RAD, Vector3f.UNIT_Z);
            
            Quaternion rotation = new Quaternion();
            
            if (bone.getName().equals("Hip")) {
               rotation = rotationY.mult(rotationX).mult(rotationZ); 
            }
            else if (bone.getName().equals("LShin")) {   
               rotation = rotationY.mult(rotationX).mult(rotationZ); 
            }
            else if (bone.getName().equals("RShin")) {   
               rotation = rotationY.mult(rotationX).mult(rotationZ); 
            }
            else if (bone.getName().equals("LFoot")) {   
               rotation = rotationZ.mult(rotationX).mult(rotationY); 
            }
            else if (bone.getName().equals("RFoot")) {   
               rotation = rotationZ.mult(rotationX).mult(rotationY); 
            }
            else{
               rotation = rotationX.mult(rotationY).mult(rotationZ); 
            }
            
            if (bone.getName().equals("Hip")) {
                bone.setUserTransforms(translation, rotation, Vector3f.UNIT_XYZ);
            }
            else{
                bone.setUserTransforms(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ);
            }

        } else if (bone != null && bvhBone != null) {
            System.out.println("Übersprungen: BVH-Name:" + bvhBone.getName() + " Bone-Name:" + bone.getName());
        } else if (bvhBone != null) {
            //System.out.println("Keinen passenden Knochen zu \"" + bvhBone.getName() + "\" im Skelett gefunden.");
        }
    }

    /**
     * Reads every Bone from the BVH-Animation-File, searches the matching
     * Bone from the Model-Skeleton and starts the Bone-Animation-Function
     * @param bvhBone 
     */
    public void traverseBVH(BVHBone bvhBone) {
        if (bvhBone != null) {
            animBone(getBone(bvhBone.getName(), m.getSkeletton().getBone("Hip")), bvhBone);
            List<BVHBone> children = bvhBone.getChildren();
            if (children != null) {
                for (BVHBone b : children) {
                    traverseBVH(b);
                }
            }
        }
    }

    /**
     * Creates the Animationsteps.
     * @param tpf TimePerFrame - defined the speed of animation depending on current FPS
     */
    public void animate(float tpf) {
        if (currentAnimation != null) {
            frame += tpf;
            if (frame > currentAnimation.animation.getFrameTime()) {

                // Animation für alle Bones ausführen
                traverseBVH(currentAnimation.animation.getHierarchy());

                currentFrame++;

                // Ende der Animation erkennen
                if (currentFrame >= currentAnimation.animation.getNbFrames()) {
                    currentFrame = 1;
                    if (nextAnimation != null) {
                        currentAnimation = nextAnimation;
                        nextAnimation = null;
                        currentFrame = 10;
                    }
                    
                }
                frame = 0;
            }
        }
    }

    /**
     * Creates a Transition between two BVH-Files
     * @return BVHController
     */
    public BVHController mkTransition() {
        Transition transition = new Transition();
        return transition.makeTransition(currentAnimation, nextAnimation, currentFrame);
    }

    /**
     * Starts the animation from the BVHController - if an old Animation is running, an automatic transition will be added!
     * @param bvh BVH-Controller to Play
     */
    public void startAnimation(BVHController bvh) {
        if (bvh.data != null) {
            // Play first Animation
            if (currentAnimation == null) {
                currentAnimation = bvh;
                currentFrame = 1;
            } 
            // Make Transition between current and next animation
            else {
                nextAnimation = bvh;
                currentAnimation = mkTransition();

                currentAnimation.animation.setNbFrames(20);
                currentFrame = 1;
            }
        } else {
            init.getGuiController().startErrorPopup("Die gewählte Animation \"" + bvh.getBVHName() + "\" wurde nicht gefunden!");
        }
    }

    /**
     * Stops the current Animation directily and freezes the last Move!
     */
    public void stopAnimation() {
        currentAnimation = null;
        currentFrame = 0;
    }

    /**
     * Returns the currently played BVHController
     * @return Current BVH-Controller
     */
    public BVHController getCurrentAnimation(){
        return currentAnimation;
    }

    /**
     * Returns the current frame from the animation.
     * @return Current frame from the animation
     */
    public int getCurrentFrame(){
        return currentFrame;
    }
}
